<template>
  <div class="big-upload">
    <a-upload :progress="progress" :fileList="fileList" @change="handleChange" :customRequest="upload">
      <a-button type="primary">
        <upload-outlined></upload-outlined>
        上传文件
      </a-button>
    </a-upload>
  </div>

</template>
<script setup lang="ts">
import sparkMD5 from 'spark-md5'
import request from '@/utils/request'
import { ref } from 'vue';
import type { UploadRequestOption, UploadProgressEvent } from 'ant-design-vue/es/vc-upload/interface';
import type { UploadProps, UploadChangeParam, UploadListProps } from 'ant-design-vue';
import { message } from 'ant-design-vue'
import 'ant-design-vue/lib/message/style/index.css'

interface UploadResponse {
  uploaded: boolean,
  url: string,
  lastSlice: string
}
interface Chunk {
  hash: string,
  name: string,
  index: number,
  chunk: Blob
}
const fileList = ref<UploadProps['fileList']>([])

const ChunkSize = 1024 * 1024 * 10
const upload = async (params: UploadRequestOption<File>) => {
  if (params.file) {
    const file = params.file as File
    // const md5Unik = await fileBuffterToMd5(params.file as File) // 唯一的md5标识 后端会建立
    const hash = await calculateHashSample(file) // 生成文件唯一的hash值
    const chunks = createFileChunk(file, ChunkSize, hash) // 分片
    const { data } = await request<UploadResponse>("/checkFile", "post", { hash })
    if (data.uploaded) {
      message.success("秒传文件成功")
      if (params.onSuccess)
        params.onSuccess(params.file as File)
      return
    }
    uploadChunks(data.lastSlice, chunks, hash, file.name.substring(file.name.lastIndexOf(".") + 1), params.onProgress as (event: UploadProgressEvent) => void, params.onSuccess as () => void)
  }
}
const progress: UploadListProps["progress"] = ref({
  strokeColor: {
    '0%': '#108ee9',
    '100%': '#87d068',
  },
  strokeWidth: 3,
  format: (percent: number) => `${parseFloat(percent.toFixed(2))}%`,
})
const handleChange = (info: UploadChangeParam) => {
  fileList.value = [...info.fileList]
};
// 进行切片
const createFileChunk = (file: File, size = ChunkSize, hash: string) => {
  const chunks = []
  let cur = 0
  let index = 0
  while (cur < file.size) {
    chunks.push({ name: hash + "-" + index + "." + file.name.substring(file.name.lastIndexOf(".") + 1), index, hash, chunk: file.slice(cur, cur + size) })
    cur += size
    index++
  }
  return chunks
}
// 计算文件的标识
const calculateHashSample = async (file: File): Promise<string> => {
  return new Promise(resolve => {
    const spark = new sparkMD5.ArrayBuffer()
    const reader = new FileReader()
    const size = file.size
    const offset = 2 * 1024 * 1024
    // 第一个2M，最后一个区块数据全要
    const chunks = [file.slice(0, offset)]
    let cur = offset
    while (cur < size) {
      if (cur + offset >= size) {
        // 最后一个区快
        chunks.push(file.slice(cur, cur + offset))
      } else {
        // 中间的区块
        const mid = cur + offset / 2
        const end = cur + offset
        chunks.push(file.slice(cur, cur + 2))
        chunks.push(file.slice(mid, mid + 2))
        chunks.push(file.slice(end - 2, end))
      }
      cur += offset
    }
    // 中间的，取前中后各2各字节
    reader.readAsArrayBuffer(new Blob(chunks))
    reader.onload = e => {
      spark.append(e?.target?.result as ArrayBuffer)
      resolve(spark.end())
    }
  })
}
const uploadChunks = async (lastSlice = "", chunks: Array<Chunk>, hash: string, ext: string, onProgress: (event: UploadProgressEvent) => void, onSuccess: () => void) => {
  const requests = chunks.map((chunk) => {
    // 转成promise
    const form = new FormData()
    form.append('chunk', chunk.chunk, chunk.name)
    form.append('hash', chunk.hash)
    form.append('name', chunk.name)
    return { form, index: chunk.index, error: 0 }
  })
  let index = lastSlice ? (+lastSlice) + 1 : 0
  const taskPool: Array<Promise<any>> = []
  const max = 6 // 设置浏览器运行最大并发数  目前6个为当前的主流
  let allProgress = index // 总进度
  while (index < requests.length) {
    const task = request("/upload", "post", requests[index].form, {
      onUploadProgress: (progress) => {
        allProgress += (progress.loaded / progress.total) // 这是单个分片的
        const percent = ((allProgress / requests.length) * 100)
        onProgress({
          percent
        })
      }
    })
    task.then(() => {
      taskPool.splice(taskPool.findIndex(item => item === task))
    })
    taskPool.push(task)
    if (taskPool.length === max) {
      await Promise.race(taskPool) // 竞赛等出一个执行完毕的请求
    }
    index++
  }
  await Promise.all(taskPool)
  await request("/mergeFile", "POST", { hash, ext })
  onSuccess()
}

// 然后开始对文件进行分割

</script>
<style scoped>
.big-upload {
  width: 400px
}
</style>
